//
//  XMTools.swift
//  XMPurchase
//
//  Created by Wxm on 2021/2/25.
// this is master change

import Foundation
import CommonCrypto
import UIKit
//import JKSwiftExtension
import CoreFoundation
let kScreenWidth = UIScreen.main.bounds.size.width
let kScreenHeight = UIScreen.main.bounds.size.height
let locallanguage = "kLanguage"
let localschme = "kLocalschme"
let localunit = "kLocalunit"
let Debug = true
let AllowLocalLog = true
//日志输出
func XMLog<T>(_ message:T,file:String = #file,funcName:String = #function,lineNum:Int = #line)  {
    
    if Debug {
        //        let path  = (file as NSString).deletingLastPathComponent
        let ext  = (file as NSString).lastPathComponent
        let logString:String = "XMLog - file:\(ext) Func:[\(funcName)] Line:\(lineNum) msg:\(message)"
        
        if AllowLocalLog {
            Logger.shared.log(logString)
        }else{
            NSLog(logString)
        }
        
    }
}

//将日志写入本地文件
class Logger {
    static let shared = Logger()

    private init() {}

    // 获取应用的文档目录路径
    private var logFilePath: String {
        let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        return documentDirectory.appendingPathComponent("app_log.txt").path
    }

    // 写日志的方法
    func log(_ message: String) {
        let logMessage = "\(Date()): \(message)\n"
        if true {
            NSLog(logMessage) // 打印到控制台
        }
        else {
            print(logMessage) // 打印到控制台
        }
       

        // 写入文件
        if let fileHandle = FileHandle(forWritingAtPath: logFilePath) {
            fileHandle.seekToEndOfFile()
            if let data = logMessage.data(using: .utf8) {
                fileHandle.write(data)
            }
            fileHandle.closeFile()
        } else {
            // 如果文件不存在，创建文件并写入日志
            do {
                try logMessage.write(toFile: logFilePath, atomically: true, encoding: .utf8)
            } catch {
                NSLog("无法写入日志文件: \(error)")
            }
        }
    }
}
//捕获系统信号，如 SIGABRT、SIGILL、SIGSEGV 等，记录崩溃日志
//在应用启动时调用
//setupUncaughtExceptionHandler()
//setupSignalHandler()
func setupUncaughtExceptionHandler() {
    NSSetUncaughtExceptionHandler { exception in
        let stackTrace = exception.callStackSymbols.joined(separator: "\n")
        let reason = exception.reason ?? "No reason"
        let name = exception.name.rawValue

        let crashLog = "Exception Name: \(name)\nReason: \(reason)\nStack Trace:\n\(stackTrace)"
        XMLog(crashLog)
    }
}

func setupSignalHandler() {
    signal(SIGABRT) { signal in
        XMLog("Received SIGABRT signal")
        exit(signal)
    }
    signal(SIGILL) { signal in
        XMLog("Received SIGILL signal")
        exit(signal)
    }
    signal(SIGSEGV) { signal in
        XMLog("Received SIGSEGV signal")
        exit(signal)
    }
    // 可以根据需要添加其他信号
}
protocol UIViewDelegate {
    var xmtop:CGFloat { get set }
    var xmbottom:CGFloat { get set }
    var xmleft:CGFloat { get set }
    var xmright:CGFloat { get set }
    var xmwidth:CGFloat { get set }
    var xmheight:CGFloat { get set }
}


extension UIView:UIViewDelegate {
    
    var xmtop: CGFloat {
        get {
            return self.frame.minY
        }
        set {
            self.frame.origin.y = newValue
        }
    }
    
    var xmbottom: CGFloat {
        get {
            return self.frame.maxY
        }
        set {
            self.frame.origin.y = newValue - self.frame.size.height
        }
    }
    
    var xmleft: CGFloat {
        get {
            return self.frame.origin.x
        }
        set {
            self.frame.origin.x = newValue
            self.frame(forAlignmentRect: CGRect(x: newValue, y: self.frame.origin.y, width: self.frame.size.width, height:self.frame.size.height ))
        }
    }
    
    var xmright: CGFloat {
        get {
            return self.frame.maxX
        }
        set {
            if self.frame.size.width <= 0 {
                print("right set warning --- 请先设置width")
            }
            self.frame.origin.x = newValue - self.frame.size.width
            self.frame(forAlignmentRect: CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width, height:self.frame.size.height ))
        }
    }
    
    var xmwidth: CGFloat {
        get {
            return self.frame.size.width
        }
        set {
            self.frame.size.width = newValue
            self.frame(forAlignmentRect: CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: newValue, height:self.frame.size.height ))
        }
    }
    
    var xmheight: CGFloat {
        get {
            return self.frame.size.height
        }
        set {
            self.frame.size.height = newValue
            self.frame(forAlignmentRect: CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width, height: newValue))
        }
    }
    
    
    //将当前视图转为UIImage
    
    func asImage() -> UIImage {
        
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        
        return renderer.image { rendererContext in
            
            layer.render(in: rendererContext.cgContext)
            
        }
    }
    /// 截屏
    ///
    /// - Parameters:
    ///   - view: 要截屏的view
    /// - Returns: 一个UIImage
    func cutImageWithView() -> UIImage
    {
        // 参数①：截屏区域  参数②：是否透明  参数③：清晰度
        UIGraphicsBeginImageContextWithOptions(self.frame.size, true, UIScreen.main.scale)
        self.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext();
        return image;
    }
}
class XMTool:NSObject {
    
    /// 是否是刘海屏
    /// - Returns: true 是刘海屏，false不是刘海屏
    static  func isFullScreen() -> Bool{
        if (self.safeDistanceTop() > 20){
            return true
        }
        return false
    }
    /// 顶部安全区高度: 非刘海屏: 20.0 刘海屏：50.0
    static func safeDistanceTop() -> CGFloat {
        if #available(iOS 13.0, *) {
            let scene = UIApplication.shared.connectedScenes.first
            guard let windowScene = scene as? UIWindowScene else { return 0 }
            guard let window = windowScene.windows.first else { return 0 }
            return window.safeAreaInsets.top
        } else if #available(iOS 11.0, *) {
            guard let window = UIApplication.shared.windows.first else { return 0 }
            return window.safeAreaInsets.top
        }
        return 0;
    }
    /// 底部安全区高度 非刘海屏:0.0 刘海屏：34.0
    static func safeDistanceBottom() -> CGFloat {
        if #available(iOS 13.0, *) {
            let scene = UIApplication.shared.connectedScenes.first
            guard let windowScene = scene as? UIWindowScene else { return 0 }
            guard let window = windowScene.windows.first else { return 0 }
            return window.safeAreaInsets.bottom
        } else if #available(iOS 11.0, *) {
            guard let window = UIApplication.shared.windows.first else { return 0 }
            return window.safeAreaInsets.bottom
        }
        return 0;
    }
    /// 顶部状态栏高度（包括安全区） 非刘海屏:20.0 刘海屏：50.0
    static func statusBarHeight() -> CGFloat {
        var statusBarHeight: CGFloat = 0
        if #available(iOS 13.0, *) {
            let scene = UIApplication.shared.connectedScenes.first
            guard let windowScene = scene as? UIWindowScene else { return 0 }
            guard let statusBarManager = windowScene.statusBarManager else { return 0 }
            statusBarHeight = statusBarManager.statusBarFrame.height
        } else {
            statusBarHeight = UIApplication.shared.statusBarFrame.height
        }
        return statusBarHeight
    }
    /// 导航栏高度 非刘海屏:44.0 刘海屏：44.0
    static func navigationBarHeight() -> CGFloat {
        return 44.0
    }
    /// 状态栏+导航栏的高度 非刘海屏: 64.0 刘海屏：94.0
    static func navigationFullHeight() -> CGFloat {
        return self.statusBarHeight() + self.navigationBarHeight()
    }

    /// 底部导航栏高度
    static func tabBarHeight() -> CGFloat {
        return 49.0
    }
    /// 底部导航栏高度（包括安全区） 非刘海屏: 49.0 刘海屏：83.0
    static func tabBarFullHeight() -> CGFloat {
        return self.tabBarHeight() + self.safeDistanceBottom()
    }
    
    /// label宽度自适应
    /// - Parameters:
    ///   - text: 文字内容
    ///   - labelHeigh: 高度
    ///   - font: 字体
    /// - Returns: 宽度
    static func autoLabelWidth(with text:String , labelHeigh: CGFloat,font:UIFont) -> CGFloat{
        
        let width = text.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height:labelHeigh ), options: [.usesFontLeading],attributes: [.font:font],context: nil).size.width
        //ceil更加准确,+1 是保证文字显示出来，某些时候不+1不行。
        return ceil(width) + 1
    }
    
    /// label高度自适应
    /// - Parameters:
    ///   - text: 文字内容
    ///   - labelWidth: 宽度
    ///   - font: 字体
    /// - Returns: 高度
    static func autoLabelHeight(with text:String , labelWidth: CGFloat,font:UIFont) -> CGFloat{
        let maxSize = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude)
        let textAttributes: [NSAttributedString.Key: Any] = [.font: font]
        
        let boundingRect = text.boundingRect(
            with: maxSize,
            options: [.usesLineFragmentOrigin, .usesFontLeading],
            attributes: textAttributes,
            context: nil
        )
        
        return ceil(boundingRect.height) + 1
    }
    static func defaultMonetaryUnitSymbol(local:String = "")->String{
        let unit = XMTool.defaultMonetaryUnit(local: local)
        var sym = "$"
        if unit == "USD"{
            sym = "$"
        }
        else if unit == "CNY"{
            sym = "¥"
        }
        else if unit == "HKD"{
            sym = "HK$"
        }
        else if unit == "INR"{
            //印度卢比
            sym = "₹"
        }
        else if unit == "JPY"{
            sym = "￥"
        }
        else if unit == "KRW"{
            sym = "₩"
        }
        return sym
    }
    static func setNav(nav:UINavigationController) {
        // 设置半透明
        nav.navigationBar.isTranslucent = true
        // navigationBar背景颜色
        nav.navigationBar.barTintColor = .white
        nav.navigationBar.tintColor = .white
        // 背景
        nav.navigationBar.setBackgroundImage(UIImage(), for: .default)
        // 隐藏navigationBar分割线
        nav.navigationBar.shadowImage = UIImage()
//        nav.navigationBar.setLineViewHidden(hidden: true)
        
        let appearance = UINavigationBar.appearance()
        let newAppearance = UINavigationBarAppearance()
        newAppearance.configureWithOpaqueBackground()
        newAppearance.backgroundColor = .white
        newAppearance.shadowImage = UIImage()
        newAppearance.shadowColor = nil
        newAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17, weight: .medium)]
        
        nav.navigationBar.standardAppearance = newAppearance
        nav.navigationBar.scrollEdgeAppearance = appearance.standardAppearance
    }
    static func defaultMonetaryUnit(local:String = "")->String{
        
        let usdf = UserDefaults.standard
        
        if local.count > 0{
            usdf.setValue(local, forKey: localunit)
            usdf.synchronize()
            return local
        }
        else{
            var language = usdf.value(forKey: localunit) as? String ?? ""
            if !(language.count > 0){
                language = "USD"
            }
            return language
            
        }
    }
    static func defaultLanguage(local:String = "")->String{
        let usdf = UserDefaults.standard
        
        if local.count > 0{
            usdf.setValue(local, forKey: locallanguage)
            usdf.synchronize()
            return local
        }
        else{
            var language = usdf.value(forKey: locallanguage) as? String ?? ""
            if !(language.count > 0){
                language = Locale.preferredLanguages.first ?? ""
            }
            return language
            
        }
    }
    static func defaultCurrentLanguageName(local:String = "")->String{
        let titleArr = ["中文（简体）","中文（繁体）","English","日本語","한국어"]
        let currentLg = XMTool.defaultLanguage(local: local)
        if currentLg.hasPrefix("en") {
            return titleArr[2]
        } else if currentLg.hasPrefix("zh") {
            if currentLg.contains("Hans") {
                //简体
                return titleArr[0]
            } else{
                //繁体
                return titleArr[1]
            }
        } else if currentLg.hasPrefix("ko") {
            return titleArr[4]
        } else if currentLg.hasPrefix("ja") {
            return titleArr[3]
        }
        else{
            return "未找到语言"
        }
    }
    static func defaultSchme(local:String = "")->String{
        let usdf = UserDefaults.standard
        
        if local.count > 0{
            usdf.setValue(local, forKey: localschme)
            usdf.synchronize()
            return local
        }
        else{
            var Schme = usdf.value(forKey: localschme) as? String ?? ""
            if !(Schme.count > 0){
                Schme = "unspecified"
            }
            return Schme
            
        }
    }
    // 获取当前应用版本号
    static func getAppVersion() -> String? {
        return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
    }

    // 获取存储在 UserDefaults 中的版本号
    static func getCachedAppVersion() -> String? {
        return UserDefaults.standard.string(forKey: "CachedAppVersion")
    }

    // 存储当前应用版本号到 UserDefaults
    static func setCachedAppVersion(_ version: String) {
        UserDefaults.standard.set(version, forKey: "CachedAppVersion")
    }

    // 比较当前应用版本号与缓存的版本号
    static func compareAppVersionWithCachedVersion() {
        if let currentVersion = getAppVersion() {
            let cachedVersion = getCachedAppVersion()
            
            if let cachedVersion = cachedVersion {
                if currentVersion != cachedVersion {
                    print("App version has changed from \(cachedVersion) to \(currentVersion).")
                    // 执行需要在版本变化时的操作，比如更新缓存
                    // ...
//                    needShowGuide = true
                } else {
                    print("App version is the same as cached version: \(currentVersion).")
                }
            } else {
                print("No cached version found. Storing current version.")
                // 缓存当前版本号
                XMTool.setMainShowGuide(true)
                XMTool.setTabbarShowGuide(true)
                setCachedAppVersion(currentVersion)
                if XMTool.isDeviceJailbroken(){
                    XMLog("该设备有越狱风险！！！")
                }
            }
        } else {
            print("Unable to get current app version.")
        }
    }

    static func getMainShowGuide() -> Bool {
        return  UserDefaults.standard.bool(forKey: "mainShowGuide")
    }
    static func setMainShowGuide(_ status: Bool) {
        UserDefaults.standard.set(status, forKey: "mainShowGuide")
    }
    static func getTabbarShowGuide() -> Bool {
        return  UserDefaults.standard.bool(forKey: "tabbarShowGuide")
    }
    static func setTabbarShowGuide(_ status: Bool) {
        UserDefaults.standard.set(status, forKey: "tabbarShowGuide")
    }
    static func imageFrom(firstLetterOf text: String, font: UIFont, textColor: UIColor, backgroundColor: UIColor) -> UIImage? {
        // 获取字符串的首字母
        guard let firstLetter = text.first else { return nil }
        
        // 创建 UILabel 并设置其属性
        let label = UILabel()
        label.text = String(firstLetter)
        label.font = font
        label.textColor = textColor
        label.backgroundColor = backgroundColor
        label.textAlignment = .center
        label.sizeToFit()

        // 使用 UIGraphicsImageRenderer 创建图像
        let renderer = UIGraphicsImageRenderer(size: label.bounds.size)
        return renderer.image { context in
            label.layer.render(in: context.cgContext)
        }
    }
    // 检查特定文件或目录
    // 越狱设备通常会有一些特定的文件和目录，非越狱设备中不存在。你可以检查以下路径
    static func isDeviceJailbroken() -> Bool {
        let fileManager = FileManager.default
        
        // 常见的越狱路径
        let jailbreakPaths = [
            "/Applications/Cydia.app",
            "/Library/MobileSubstrate/MobileSubstrate.dylib",
            "/bin/bash",
            "/usr/sbin/sshd",
            "/etc/apt"
        ]
        
        for path in jailbreakPaths {
            if fileManager.fileExists(atPath: path) {
                return true
            }
        }
        if isDeviceJailbrokenOne()  || isDeviceJailbrokenTwo(){
            return true
        }
        return false
    }
   // 检测 dyld 库
   // 如果设备被越狱，通常会修改一些系统库，您可以通过检查一些动态链接库的行为来进一步检测。
    static func isDeviceJailbrokenOne() -> Bool {
        // 检查是否能加载某些越狱相关的库
        if let _ = dlopen("/usr/lib/libsubstrate.dylib", RTLD_NOW) {
            return true
        }
        
        return false
    }
    // 检查是否可以访问受限制的目录
    // 越狱的设备可以访问系统文件，这些文件通常受到保护。在未越狱的设备上，尝试访问这些目录会失败。
    static func isDeviceJailbrokenTwo() -> Bool {
        let path = "/private/var/lib/apt"
        do {
            let fileManager = FileManager.default
            let attributes = try fileManager.attributesOfItem(atPath: path)
            return attributes.count > 0
        } catch {
            return false
        }
    }

}
extension Array {
    
    // 去重
    func filterDuplicates<E: Equatable>(_ filter: (Element) -> E) -> [Element] {
        var result = [Element]()
        for value in self {
            let key = filter(value)
            if !result.map({filter($0)}).contains(key) {
                result.append(value)
            }
        }
        return result
    }
    
}
extension String {
    //    如果需要小写，将"%02X"改成"%02x"
    var MD5:String {
        let utf8 = cString(using: .utf8)
        var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        //ios13 后不推荐md5了，推荐SHA256
        CC_MD5(utf8, CC_LONG(utf8!.count - 1), &digest)
        //        CC_SHA256(utf8, CC_LONG(utf8!.count - 1), &digest)
        return digest.reduce("") { $0 + String(format:"%02X", $1) }
    }
    var md5:String {
        let utf8 = cString(using: .utf8)
        var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        //ios13 后不推荐md5了，推荐SHA256
        CC_MD5(utf8, CC_LONG(utf8!.count - 1), &digest)
        //        CC_SHA256(utf8, CC_LONG(utf8!.count - 1), &digest)
        return digest.reduce("") { $0 + String(format:"%02x", $1) }
    }
    var SHA256:String{
        let utf8 = cString(using: .utf8)
        var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        //ios13 后不推荐md5了，推荐SHA256
        CC_SHA256(utf8, CC_LONG(utf8!.count - 1), &digest)
        return digest.reduce("") { $0 + String(format:"%02X", $1) }
    }
    var sha256:String{
        let utf8 = cString(using: .utf8)
        var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        //ios13 后不推荐md5了，推荐SHA256
        CC_SHA256(utf8, CC_LONG(utf8!.count - 1), &digest)
        return digest.reduce("") { $0 + String(format:"%02x", $1) }
    }
    func getArrayOrDicFromJSONString() -> Any {
        
        let jsonData:Data = self.data(using: .utf8)!
        
        //可能是字典也可能是数组，再转换类型就好了
        if let info = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) {
            return info
        }
        return ""
    }
    func isMobile() -> Bool {
        var ismobile = false
        let mobileReg = "^1\\d{10}$"
        let regextestmobile = NSPredicate(format: "SELF MATCHES %@",mobileReg)
        if regextestmobile.evaluate(with: self) { ismobile = true}
        return ismobile;
    }
}
class XMFileManager: NSObject {
   // LFU (Least Frequently Used) 是一种缓存替换算法，用于管理有限空间内的缓存数据。它的核心思想是优先淘汰使用频率最少的缓存项，即缓存中被访问次数最少的条目将最先被移除。
    //LRU 基于最近的访问时间进行淘汰，而 LFU 基于访问频率进行淘汰。
    //LRU 更适合访问频率变化较快的场景，而 LFU 更适合长期稳定的访问模式。
    // 节点类
    class DoublyLinkedListNode<T> {
        var value: T
        var next: DoublyLinkedListNode?
        weak var prev: DoublyLinkedListNode?

        init(value: T) {
            self.value = value
        }
    }

    // 双向链表类
    class DoublyLinkedList<T> {
        private var head: DoublyLinkedListNode<T>?
        private var tail: DoublyLinkedListNode<T>?
        private(set) var count: Int = 0
        
        var isEmpty: Bool {
            return head == nil
        }

        var first: DoublyLinkedListNode<T>? {
            return head
        }

        var last: DoublyLinkedListNode<T>? {
            return tail
        }

        // 向链表尾部添加新元素
        func append(value: T) {
            let newNode = DoublyLinkedListNode(value: value)
            if let tailNode = tail {
                newNode.prev = tailNode
                tailNode.next = newNode
            } else {
                head = newNode
            }
            tail = newNode
            count += 1
        }

        // 向链表头部插入新元素
        func prepend(value: T) {
            let newNode = DoublyLinkedListNode(value: value)
            if let headNode = head {
                newNode.next = headNode
                headNode.prev = newNode
            } else {
                tail = newNode
            }
            head = newNode
            count += 1
        }

        // 删除节点
        func remove(_ node: DoublyLinkedListNode<T>) {
            let prevNode = node.prev
            let nextNode = node.next

            if let prevNode = prevNode {
                prevNode.next = nextNode
            } else {
                head = nextNode
            }

            if let nextNode = nextNode {
                nextNode.prev = prevNode
            } else {
                tail = prevNode
            }

            node.prev = nil
            node.next = nil
            count -= 1
        }

        // 移除链表头部元素
        func removeFirst() -> T? {
            guard let firstNode = head else { return nil }
            remove(firstNode)
            return firstNode.value
        }

        // 移除链表尾部元素
        func removeLast() -> T? {
            guard let lastNode = tail else { return nil }
            remove(lastNode)
            return lastNode.value
        }
        
        // 通过索引访问节点
        func node(at index: Int) -> DoublyLinkedListNode<T>? {
            guard index >= 0 && index < count else { return nil }
            var node: DoublyLinkedListNode<T>?
            if index < count / 2 {
                node = head
                for _ in 0..<index {
                    node = node?.next
                }
            } else {
                node = tail
                for _ in stride(from: count - 1, to: index, by: -1) {
                    node = node?.prev
                }
            }
            return node
        }
        
        // 通过索引访问节点的值
        func value(at index: Int) -> T? {
            return node(at: index)?.value
        }
        
        // 反向遍历
        func reverseTraversal() -> [T] {
            var result = [T]()
            var currentNode = tail
            while let node = currentNode {
                result.append(node.value)
                currentNode = node.prev
            }
            return result
        }
    }

    /// 用户偏好设置
    /// - Parameters:
    ///   - value:value
    ///   - key: key
    /// - Returns: 是否成功
    func saveToUserDf(value:Any,key:String) -> Bool {
        do {
            if #available(iOS 11.0, *) {
                let data = try NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
                UserDefaults.standard.set(data, forKey: key)
                return UserDefaults.standard.synchronize()
            } else {
                let data = NSKeyedArchiver.archivedData(withRootObject: value)
                UserDefaults.standard.set(data, forKey: key)
                return UserDefaults.standard.synchronize()
                // Fallback on earlier versions
            }
            
        } catch  {
            print("模型转data失败: \(error)")
        }
        return false
        
    }
    func getFromUserDf(key:String) -> Any? {
        
        let data:Data? = UserDefaults.standard.value(forKey: key) as? Data
        if let data = data {
            do {
                let model:Any? = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) ?? nil
                return model
            } catch  {
                
            }
        }
        
        return nil
    }
    
    func removeUserDf(key:String)  {
        UserDefaults.standard.removeObject(forKey: key)
        XMLog("删除UserDf-成功")
    }
    /// 归档操作
    /// - Returns: 是否成功
    func saveToArchiver(obj:Any,fileName:String) -> Bool {
        let file = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last!
        // 拼接路径 自动带斜杠的
        let filePath = (file as NSString).appendingPathComponent( fileName + ".archiver")
        print("用户信息路径:\(filePath)")
        // 保存
        do {
            if #available(iOS 11.0, *) {
                let data = try NSKeyedArchiver.archivedData(withRootObject: obj, requiringSecureCoding: true)
                do {
                    _ = try data.write(to: URL(fileURLWithPath: filePath))
                    print("saveToArchiver写入成功")
                    return true
                } catch {
                    print("data写入本地失败: \(error)")
                }
            }else {
                let data = NSKeyedArchiver.archivedData(withRootObject: obj)
                do {
                    _ = try data.write(to: URL(fileURLWithPath: filePath))
                    print("saveToArchiver写入成功")
                    return true
                } catch {
                    print("data写入本地失败: \(error)")
                }
            }
            
        } catch  {
            print("模型转data失败: \(error)")
        }
        return false
    }
    
    /// 查询归档
    /// - Parameter fileName: 名称
    /// - Returns: 数据
    func getArchiver(fileName:String) -> Any? {
        // 路径
        let file = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last!
        // 拼接路径 自动带斜杠的
        let filePath = (file as NSString).appendingPathComponent( fileName + ".archiver")
        do {
            let data = try Data.init(contentsOf: URL(fileURLWithPath: filePath))
            // 当用户首次登陆, 直接从沙盒获取数据, 就会为nil  所以这里需要使用as?
            let model:Any? = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) ?? nil
            return model
        } catch {
            print("获取data数据失败: \(error)")
        }
        return nil
    }
    
    /// 删除Archiver本地文件
    /// - Parameter fileName: path
    func deleteArchiver(fileName:String) {
        let file = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last!
        // 拼接路径 自动带斜杠的
        let filePath = (file as NSString).appendingPathComponent( fileName + ".archiver")
        do {
            try FileManager.default.removeItem(at: URL(fileURLWithPath: filePath))
            XMLog("删除Archiver本地文件-成功")
        } catch  {
            
        }
    }
    
    /// 保存到钥匙串
    /// - Parameters:
    ///   - key: key
    ///   - value: Data类型数据
    func savetoKeyChain(key:String,value:Any) {
        
        do {
            var valueData :Data? = nil
            if #available(iOS 11.0, *) {
                valueData = try NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
            }else {
                valueData = NSKeyedArchiver.archivedData(withRootObject: value)
            }
            let service = Bundle.main.bundleIdentifier!
            let secItem = [
                kSecClass as NSString : kSecClassGenericPassword as NSString,
                kSecAttrService as NSString : service,
                kSecAttrAccount as NSString : key,
                kSecValueData : valueData!
            ] as NSDictionary
            
            var result:CFTypeRef?
            let status = Int(SecItemAdd(secItem, &result))
            switch status {
            case Int(errSecSuccess):
                print("keychain - Successfully stored the value")
            case Int(errSecDuplicateItem):
                print("keychain - this item is already saved, Cannot duplicate it")
                self.updateKeyChain(keyToSearchfor: key, newValue: value)
            default:
                print("keychain - An error occurred with code\(status)")
            }
        } catch  {
            print("keychain - An error occurred with code\(error)")
        }
        
    }
    
    //在钥匙串中查找数据
    
    /****查找钥匙串中的值
     
     SecItemCopyMatching(CFDictionary, UnsafeMutablePointer<CFTypeRef?>?)
     
     1.构建一个字典，天剑kSecClass键，设置键的值来标识查找项的类型。 例如：kSecClassGenericPassword
     
     2.添加kSecAttrService键。取值为查找项服务的字符串，所有应用应采用相同的值，这样任意应用写到钥匙串的数据，其他应用可以访问
     
     3.添加kSecAttrAccount键，取值为钥匙串已存储项对应的键
     
     4.获取特定属性的值：创建修改日期，需要向字典中添加kSecReturnAttributes,并将其值设置为kCFBooleanTrue
     如果设置CFDictionary键为 kSecReturnAttributes键，则返回值为nil或CFDictionaryRef隐含类型
     如果为kSecReturnData添加到字典，返回类型是CDDataRef
     **/
    
    func queryFromKeyChain(keyToSearchfor:String) -> Any{
        let service = Bundle.main.bundleIdentifier
        let query = [kSecClass as NSString : kSecClassGenericPassword as NSString,
                     kSecAttrAccount as NSString : keyToSearchfor,
                     kSecAttrService as NSString : service!,
                     kSecReturnAttributes as NSString : kCFBooleanTrue!
        ] as NSDictionary
        
        var valueAttributes : CFTypeRef?
        let results = Int(SecItemCopyMatching(query, &valueAttributes))
        if results == Int(errSecSuccess) {
            
            let attributes = valueAttributes! as! NSDictionary
            
            //            let key = attributes[kSecAttrAccount as NSString] as! String
            //            let accessGroup = attributes[kSecAttrAccessGroup as NSString] as! String
            //            let createDate = attributes[kSecAttrCreationDate as NSString] as! NSDate
            //            let modifiedDate = attributes[kSecAttrModificationDate as NSString] as! NSDate
            //            let serviceValue = attributes[kSecAttrService as NSString] as! String
            //            print(attributes)
            //            print(key)
            //            print(accessGroup)
            //            print(createDate)
            //            print(modifiedDate)
            //            print(serviceValue)
            return attributes
            
            
        }else{
            print("Error happened with code:\(results)")
        }
        return ["Error":""]
    }
    
    /// 取出钥匙串的数据
    /// - Parameter keyToSearchfor: key
    /// - Returns: value
    func queryDataFromKeyChain(keyToSearchfor:String) -> Any?{
        let service = Bundle.main.bundleIdentifier
        let query = [kSecClass as NSString : kSecClassGenericPassword as NSString,
                     kSecAttrAccount as NSString : keyToSearchfor,
                     kSecAttrService as NSString : service!,
                     kSecReturnData as NSString : kCFBooleanTrue!
        ] as NSDictionary
        
        var returnedData : CFTypeRef?
        let results = Int(SecItemCopyMatching(query, &returnedData))
        if results == Int(errSecSuccess) {
            let data = returnedData! as! Data
            do {
                let ret = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
                return ret ?? nil
            } catch  {
                return nil
            }
        }else{
            print("Error happened with code:\(results)")
        }
        return nil
    }
    
    /// 更新钥匙串
    /// - Parameters:
    ///   - keyToSearchfor: key
    ///   - value: newvalue
    func updateKeyChain(keyToSearchfor:String,newValue:Any) {
        
        let service = Bundle.main.bundleIdentifier
        
        do {
            var newData:Data? = nil
            if #available(iOS 11.0, *) {
                newData = try NSKeyedArchiver.archivedData(withRootObject: newValue, requiringSecureCoding: true)
            }else{
                newData =  NSKeyedArchiver.archivedData(withRootObject: newValue)
            }
            
            let query = [kSecClass as NSString: kSecClassGenericPassword as NSString,
                         kSecAttrService as NSString: service as Any,
                         kSecAttrAccount as NSString: keyToSearchfor] as NSDictionary
            
            var result: CFTypeRef?
            let found = Int(SecItemCopyMatching(query, &result))
            if found == Int(errSecSuccess){
                
                let update = [kSecValueData as NSString: newData! ,
                              kSecAttrComment as NSString : "my comments"] as NSDictionary
                
                let updated = Int(SecItemUpdate(query, update))
                if updated == Int(errSecSuccess){
                    print("Successfully updated the existing value")
                    readExistingValue();
                } else {
                    print("failed to update the value. error = \(updated)")
                }
            }else{
                print("error happened. Code=\(found)")
            }
        }
        catch {
            print(error)
        }
        
    }
    
    /// 删除钥匙串
    /// - Parameter keyToSearchFor: key
    func deleteKeychian(keyToSearchFor:String){
        let service = Bundle.main.bundleIdentifier
        
        let query = [kSecClass as NSString: kSecClassGenericPassword as NSString,
                     kSecAttrService as NSString: service as Any,
                     kSecAttrAccount as NSString: keyToSearchFor] as NSDictionary
        
        let found = Int(SecItemDelete(query))
        if found == Int(errSecSuccess){
            print("钥匙串删除成功")
        }
        
    }
    //更新多个值
    func readExistingValue() {
        
    }
}

//enum MaskType {
//    //显示HUD的同时允许用户点击其他地方
//    case LKProgressHUDMaskTypeNone
//    //不允许用户点击其他地方
//    case LKProgressHUDMaskTypeClear
//    //不允许用户点击其他地方,并且添加灰色覆盖背景
//    case LKProgressHUDMaskTypeBlack
//    //不允许用户点击其他地方,并且添加渐变覆盖背景
//    case LKProgressHUDMaskTypeGradient
//}
//class XMProgress: UIView {
//    static func show() {
//
//    }
//}
extension Date {
    
    /// 获取当前 秒级 时间戳 - 10位
    var timeStamp : String {
        let timeInterval: TimeInterval = self.timeIntervalSince1970
        let timeStamp = Int(timeInterval)
        return "\(timeStamp)"
    }
    
    /// 获取当前 毫秒级 时间戳 - 13位
    var milliStamp : String {
        let timeInterval: TimeInterval = self.timeIntervalSince1970
        let millisecond = CLongLong(round(timeInterval*1000))
        return "\(millisecond)"
    }
}
    
extension UIColor {
    static func RGBA(_ r: CGFloat,_ g:CGFloat,_ b:CGFloat,_ a:CGFloat) -> UIColor{
        return  UIColor.init(red: r / 255.0, green: g / 255.0, blue: b / 255.0, alpha: a)
    }
    /// 16进制转color UIColor().hexStringToColor(hexString: "#6CB8FF")
    /// - Parameter hexString: 16进制字符串
    /// - Returns: 颜色
    static func hexStringToColor(hexString: String) -> UIColor{
        
        var cString: String = hexString.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
        
        if cString.count < 6 {
            return UIColor.black
        }
        if cString.hasPrefix("0X") || cString.hasPrefix("0x") {
            cString.remove(at: cString.index(cString.startIndex, offsetBy: 2))
            //            cString.removeFirst(2) 6CB8FF
            //            cString.removeLast(6) 0X
        }
        if cString.hasPrefix("#") {
            cString.removeFirst()
        }
        if cString.count != 6 {
            return UIColor.black
        }
        
        var range: NSRange = NSMakeRange(0, 2)
        let rString = (cString as NSString).substring(with: range)
        range.location = 2
        let gString = (cString as NSString).substring(with: range)
        range.location = 4
        let bString = (cString as NSString).substring(with: range)
        
        var r: UInt64 = 0x0
        var g: UInt64 = 0x0
        var b: UInt64 = 0x0
        Scanner.init(string: rString).scanHexInt64(&r)
        Scanner.init(string: gString).scanHexInt64(&g)
        Scanner.init(string: bString).scanHexInt64(&b)
        
        if #available(iOS 10.0, *) {
            return UIColor(displayP3Red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, blue: CGFloat(b)/255.0, alpha: CGFloat(1))
        } else {
            // Fallback on earlier versions
            return UIColor(red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, blue: CGFloat(b)/255.0, alpha: CGFloat(1))
        }
        
    }
}

extension Bundle {
    static var XMBundle:Bundle{
        return Bundle.main
//        //        let xmpath = Bundle.main.path(forResource: "Frameworks/OKDYSDK.framework/OKBundle", ofType: "bundle") ?? "NOT Find OKBundle"
//        // 创建资源bundle实例对象
//        let momdName = "OKBundle" //pass this as a parameter
//        
//        guard let xmpath = Bundle(for: type(of: UIApplication.shared.self)).url(forResource: momdName, withExtension:"bundle") else {
//            //            fatalError("NOT Find OKBundle")
//            return Bundle(url: Bundle.main.url(forResource: "OKBundle", withExtension: "bundle") ?? URL(filePath: "")) ?? Bundle.main
//        }
//        guard let bundle = Bundle(url: xmpath) else { return Bundle.main }
//        return bundle
    }
    static func XMBundleImg(imgName:String) -> UIImage{
        guard let xmpath = Bundle.XMBundle.path(forResource: imgName, ofType: "png") else {
            //            fatalError("NOT Find OKBundle")
            return UIImage.init(named: "WX") ?? UIImage()
        }
        
        return UIImage.init(contentsOfFile:xmpath) ?? UIImage.init(named: "WX")!
    }
    /// 初始化配置文件
    static var XMBundleConfig:[String:String]? = {()-> [String:String]? in
        var dic:[String:String] = NSMutableDictionary.init() as! [String : String]
        let path = Bundle.XMBundle.path(forResource: "ok_config", ofType: "cfg") ?? ""
        //        print("OKBundle path = " + path)
        //        do{
        //            let contentStr = try String.init(contentsOfFile: path, encoding: .utf8)
        //        }
        //        catch{
        //
        //        }
        guard let contentStr = try? String.init(contentsOfFile: path, encoding: .utf8) else {
            XMLog("配置文件不能为空")
            //            assert(true, "配置文件不能为空")
            return nil
        }
        let array = contentStr.components(separatedBy: "\n")
        for item:String in array {
            var temp = item
            temp = temp.replacingOccurrences(of: " ", with: "")
            temp = temp.replacingOccurrences(of: "\n", with: "")
            temp = temp.replacingOccurrences(of: "\r", with: "")
            let keyValue:Array<String> = temp.components(separatedBy: "=")
            if keyValue.count == 2 {
                dic.updateValue(keyValue.last!, forKey: keyValue.first!)
            }
        }
        return dic
    }()
}
extension UIApplication {
    
    var keyWindow: UIWindow? {
        // Get connected scenes
        return UIApplication.shared.connectedScenes
            // Keep only active scenes, onscreen and visible to the user
            .filter { $0.activationState == .foregroundActive }
            // Keep only the first `UIWindowScene`
            .first(where: { $0 is UIWindowScene })
            // Get its associated windows
            .flatMap({ $0 as? UIWindowScene })?.windows
            // Finally, keep only the key window
            .first(where: \.isKeyWindow)
    }
    var keyWindowPresentedController: UIViewController? {
            var viewController = self.keyWindow?.rootViewController
            
            // If root `UIViewController` is a `UITabBarController`
            if let presentedController = viewController as? UITabBarController {
                // Move to selected `UIViewController`
                viewController = presentedController.selectedViewController
            }
            
            // Go deeper to find the last presented `UIViewController`
            while let presentedController = viewController?.presentedViewController {
                // If root `UIViewController` is a `UITabBarController`
                if let presentedController = presentedController as? UITabBarController {
                    // Move to selected `UIViewController`
                    viewController = presentedController.selectedViewController
                } else {
                    // Otherwise, go deeper
                    viewController = presentedController
                }
            }
            
            return viewController
        }
    
}

extension UIViewController {
    static func currentCtrView(completion: @escaping (UIView?) -> UIView?) {
        DispatchQueue.main.async {
            // Perform some task
            let contentView = UIViewController.current().view
            _ = completion(contentView)
        }
    }
    static func frontView() {
        UIViewController.currentCtrView {front in
           return front
        }
    }
    /** 获取当前控制器 */
    static func current() -> UIViewController {
        if #available(iOS 15, *) {
            return UIApplication.shared.keyWindowPresentedController ?? UIViewController()
        }else{
            let vc = UIApplication.shared.keyWindow?.rootViewController ??  UIApplication.shared.windows.first?.rootViewController
            let current = UIViewController.findBest(vc: vc!)
            return current
        }
        
    }
    /** 获取最上层控制器 */
    static func findTopViewController(_ rootViewController: UIViewController) -> UIViewController {
        if let presentedVC = rootViewController.presentedViewController {
            return findTopViewController(presentedVC)
        } else if let navController = rootViewController as? UINavigationController, let topVC = navController.topViewController {
            return findTopViewController(topVC)
        } else if let tabController = rootViewController as? UITabBarController, let selectedVC = tabController.selectedViewController {
            return findTopViewController(selectedVC)
        } else {
            return rootViewController
        }
    }
    private static func findBest(vc: UIViewController) -> UIViewController {
        
        if vc.presentedViewController != nil {
            return UIViewController.findBest(vc: vc.presentedViewController!)
        } else if vc.isKind(of: UISplitViewController.self) {
            let svc = vc as! UISplitViewController
            if svc.viewControllers.count > 0 {
                return UIViewController.findBest(vc: svc.viewControllers.last!)
            } else {
                return vc
            }
        } else if vc.isKind(of: UINavigationController.self) {
            let svc = vc as! UINavigationController
            if svc.viewControllers.count > 0 {
                return UIViewController.findBest(vc: svc.topViewController!)
            } else {
                return vc
            }
        } else if vc.isKind(of: UITabBarController.self) {
            let svc = vc as! UITabBarController
            if (svc.viewControllers?.count ?? 0) > 0 {
                return UIViewController.findBest(vc: svc.selectedViewController!)
            } else {
                return vc
            }
        } else {
            return vc
        }
    }
}
class LanguesTool: NSObject {
    @objc  static func LocalizedStringForKey(key:String, value:String = "",table:String = "Localizable") -> String {
        var bundle:Bundle!
//        var language = Locale.preferredLanguages.first
        var language = XMTool.defaultLanguage()
        if language.hasPrefix("en") {
            language = "en"
        } else if language.hasPrefix("zh") {
            if language.contains("Hans") {
                //简体
                language = "zh-Hans";
            } else{
                //繁体
                language = "zh-Hant";
            }
        } else if language.hasPrefix("ko") {
            language = "ko"
        } else if language.hasPrefix("ja") {
            language = "ja"
        }
        else if language.hasPrefix("it") {
            language = "it"
            //这儿可以添加其他的语言
        }
        else{
            language = "en"
        }
        //        #warning("要注释下面的")
        //        language = "it"
        //EmotionIconResource是bundle文件名
        let path = Bundle.XMBundle.path(forResource: language, ofType: "lproj")!
        bundle = Bundle.init(path: path )
        let newValue = bundle.localizedString(forKey: key, value: value, table: table)
        return newValue
    }
}


extension Dictionary {
    static func toString(params:[String:Any]) -> String {
        let data = try? JSONSerialization.data(withJSONObject: params, options: [])
        guard let str = String(data: data!, encoding: String.Encoding.utf8) else { return "nil" }
        return str
    }
    static func toOnlyString(params:[String:Any]) -> String {
        if params.count == 0 {
            return ""
        }
        var str = ""
        for item in params {
            if item.value is String {
                str = str.appending(item.key).appending("=").appending((item.value as! String)).appending("&")
            }else if item.value is NSNumber {
                let tempStr:String = (item.value as! NSNumber).stringValue
                str = str.appending(item.key).appending("=").appending(tempStr).appending("&")
            }else{
                let tempStr:String = String(item.value as! Int)
                str = str.appending(item.key).appending("=").appending(tempStr).appending("&")
            }
            
        }
        str.remove(at: str.index(str.endIndex, offsetBy: -1))
        return str
    }
}

import UIKit
import Photos
class XMPhoneScreenshot {
    //相册权限查询
    func checkPhotoLibraryPermission(){

        if #available(iOS 14, *) {
            let requiredAccessLevel: PHAccessLevel = .addOnly
            PHPhotoLibrary.requestAuthorization(for: requiredAccessLevel) { (authorizationStatus) in
                switch authorizationStatus{
                case .limited:
                    break
                case .authorized:
                    break
                default:
                    break
                }
            }
        } else {
            
            let status = PHPhotoLibrary.authorizationStatus()
            switch status {
            case .authorized:  //已授权
                break
            case .notDetermined,.restricted,.denied,.limited:
                PHPhotoLibrary.shared().performChanges({
                    
                }, completionHandler: { (isSuccess, error) in
                    
                })
                break
            @unknown default:
                break
            }
        }
    }
    func saveImage(image: UIImage) {
        
        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.creationRequestForAsset(from: image)
        }, completionHandler: { (isSuccess, error) in
            
            DispatchQueue.main.async {
                
                if isSuccess {// 成功
                    print("Success")
                   
                }
            }
        })
    }
    
    func saveImageToPhotoLibrary(image: UIImage?) {
        
        guard let img = image else {
            return
        }
        // 判断权限
        if #available(iOS 14, *) {
            
            switch PHPhotoLibrary.authorizationStatus(for: .readWrite) {
            case .authorized :
                self.saveImage(image: img)
            case .notDetermined:
                PHPhotoLibrary.requestAuthorization { [weak self](status) in
                    if status == .authorized {
                        
                        self?.saveImage(image: img)
                    } else {
                        print("User denied")
                    }
                }
                break
                
            case .restricted,.denied,.limited:
                if let url = URL.init(string: UIApplication.openSettingsURLString) {
                    if UIApplication.shared.canOpenURL(url) {
                        UIApplication.shared.open(url)
                    }
                }
                break
            @unknown default: break
                
            }
        } else {
            // Fallback on earlier versions
            switch PHPhotoLibrary.authorizationStatus(){
            case .authorized :
                self.saveImage(image: img)
            case .notDetermined:
                PHPhotoLibrary.requestAuthorization { [weak self](status) in
                    if status == .authorized {
                        
                        self?.saveImage(image: img)
                    } else {
                        print("User denied")
                    }
                }
                break
                
            case .restricted,.denied,.limited:
                if let url = URL.init(string: UIApplication.openSettingsURLString) {
                    if UIApplication.shared.canOpenURL(url) {
                        UIApplication.shared.open(url)
                    }
                }
                break
            @unknown default: break
                
            }
        }
        
    }
}
extension UIDevice {
    
    public class func isPad() -> Bool {
        
        return UIDevice.current.userInterfaceIdiom == .pad
        
    }
    
    public class func isPhone() -> Bool {
        
        return UIDevice.current.userInterfaceIdiom == .phone
        
    }
    
}

extension UITapGestureRecognizer {
    func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
        guard let attributedString = label.attributedText else { return false }

        let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
        let textContainer = NSTextContainer(size: label.bounds.size)
        textContainer.lineFragmentPadding = 0
        textContainer.lineBreakMode = label.lineBreakMode
        textContainer.maximumNumberOfLines = label.numberOfLines

        let layoutManager = NSLayoutManager()
        layoutManager.addTextContainer(textContainer)

        let textStorage = NSTextStorage(attributedString: mutableAttributedString)
        textStorage.addLayoutManager(layoutManager)

        let locationOfTouchInLabel = self.location(in: label)
        let textBoundingBox = layoutManager.usedRect(for: textContainer)
        let textContainerOffset = CGPoint(
            x: (label.bounds.size.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
            y: (label.bounds.size.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y
        )

        let locationOfTouchInTextContainer = CGPoint(
            x: locationOfTouchInLabel.x - textContainerOffset.x,
            y: locationOfTouchInLabel.y - textContainerOffset.y
        )

        let indexOfCharacter = layoutManager.characterIndex(
            for: locationOfTouchInTextContainer,
            in: textContainer,
            fractionOfDistanceBetweenInsertionPoints: nil
        )

        return NSLocationInRange(indexOfCharacter, targetRange)
    }
}
